package mint.modules.generator.expression;

import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;

import org.antlr.runtime.tree.Tree;

import mint.modules.generator.CounterManager;
import mint.modules.generator.Scope;
import mint.modules.type.Type;

public class AdditiveExprNode extends Expression {
	private Expression left, right;
	private Type type;
	private int line;
	private List<String> errors;
	private String operator;

	public AdditiveExprNode(Scope theParent, ExpressionType exprType, Tree additiveTree) {
		super(theParent, exprType);
		
		type = null;
		line = additiveTree.getLine();
		errors = new ArrayList<String>();
		operator = null;
		
		if (additiveTree.getText().equals("+") || 
			additiveTree.getText().equals("-"))
		{
			left = new AdditiveExprNode(theParent, exprType, additiveTree.getChild(0));
			right = new MultiplicativeExprNode(theParent, exprType, additiveTree.getChild(1));
			operator = additiveTree.getText();
			CounterManager.getInstance().incrementParameter();
		}
		else
		{
			left = new MultiplicativeExprNode(theParent, exprType, additiveTree);
			right = null;
		}
	}

	@Override
	public void testSemantic() {
		left.testSemantic();
		type = left.getType();
		
		if (right != null)
		{
			if (getExpressionType() != ExpressionType.Right)
				errors.add(line + ": " + operator + " must be used on the right side of a expression");
			right.testSemantic();
			
			Type typeLeft = left.getType();
			Type typeRight = right.getType();			
			
			if (operator.equals("-"))
			{
				if (typeLeft.equals(Type.intType))
				{
					if (typeRight.equals(Type.intType))
						type = Type.intType;
					else if (typeRight.equals(Type.floatType))
						type = Type.floatType;
					else
					{
						type = Type.intType;
						errors.add(line + ": " + operator + " must be used on integer or float expressions");
					}
				}
				else if (typeLeft.equals(Type.floatType))
				{
					if (typeRight.equals(Type.intType))
						type = Type.floatType;
					else if (typeRight.equals(Type.floatType))
						type = Type.floatType;
					else
					{
						type = Type.intType;
						errors.add(line + ": " + operator + " must be used on integer or float expressions");
					}
				}
				else
				{
					type = Type.intType;
					errors.add(line + ": " + operator + " must be used on integer or float expressions");
				}
			} else if (operator.equals("+"))
			{
				if (typeLeft.equals(Type.intType))
				{
					if (typeRight.equals(Type.intType))
						type = Type.intType;
					else if (typeRight.equals(Type.floatType))
						type = Type.floatType;
					else if (typeRight.equals(Type.stringType))
					{
						type = Type.intType;
						errors.add(line + ": " + operator + " must be string in both sides");
					}
					else
					{
						type = Type.intType;
						errors.add(line + ": " + operator + " must be used on integer string or float expressions");
					}
				}
				else if (typeLeft.equals(Type.floatType))
				{
					if (typeRight.equals(Type.intType))
						type = Type.floatType;
					else if (typeRight.equals(Type.floatType))
						type = Type.floatType;
					else if (typeRight.equals(Type.stringType))
					{
						type = Type.floatType;
						errors.add(line + ": " + operator + " must be string in both sides");
					}
					else
					{
						type = Type.intType;
						errors.add(line + ": " + operator + " must be used on integer, string or float expressions");
					}
				} else if (typeLeft.equals(Type.stringType))
				{
					if (typeRight.equals(Type.stringType))
						type = Type.stringType;
					else
					{
						type = Type.stringType;
						errors.add(line + ": " + operator + " must be string in both sides");
					}
				}
				else
				{
					type = Type.intType;
					errors.add(line + ": " + operator + " must be used on integer or float expressions");
				}
			}
		}
	}

	@Override
	public List<String> getSemanticErrors() {
		errors.addAll(left.getSemanticErrors());
		if (right != null)
			errors.addAll(right.getSemanticErrors());
		return errors;
	}

	@Override
	public void emit(Formatter output) {		
		if (operator != null)
		{
			Type typeLeft = left.getType();
			Type typeRight = right.getType();			
			
			if (operator.equals("-"))
			{
				left.emit(output);
				
				if (typeLeft.equals(Type.intType))
				{
					if (typeRight.equals(Type.intType))
					{
						right.emit(output);
						output.format("isub\n");
					}
					else if (typeRight.equals(Type.floatType))
					{
						output.format("i2f\n");
						right.emit(output);
						output.format("fsub\n");						
					}
				}
				else if (typeLeft.equals(Type.floatType))
				{
					right.emit(output);
					if (typeRight.equals(Type.intType))
						output.format("i2f\n");
					output.format("fsub\n");
				}
			} if (operator.equals("+"))	{
				if (typeLeft.equals(Type.intType))
				{
					left.emit(output);
					if (typeRight.equals(Type.intType))
					{
						right.emit(output);
						output.format("iadd\n");
					}
					else if (typeRight.equals(Type.floatType))
					{
						output.format("i2f\n");
						right.emit(output);
						output.format("fadd\n");						
					}
				}
				else if (typeLeft.equals(Type.floatType))
				{
					left.emit(output);
					right.emit(output);
					if (typeRight.equals(Type.intType))
						output.format("i2f\n");
					output.format("fadd\n");
				} else if (typeLeft.equals(Type.stringType))
				{
					output.format("new java/lang/StringBuffer\n");
					output.format("dup\n");
					output.format("invokespecial java/lang/StringBuffer/<init>()V\n");					
					left.emit(output);
					output.format("invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;\n");					
					right.emit(output);
					output.format("invokevirtual java/lang/StringBuffer/append(Ljava/lang/String;)Ljava/lang/StringBuffer;\n");
					output.format("invokevirtual java/lang/StringBuffer/toString()Ljava/lang/String;\n");
				}
			}
		} else
			left.emit(output);
	}

	@Override
	public int getLine() {
		return line;
	}

	@Override
	public Type getType() {		
		return type;
	}

	@Override
	public void leftEmit(Formatter output) {
		left.leftEmit(output);		
	}
}
